﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace CARMSBusiness 
{
    /// <summary>
    /// Xml文档处理的公用方法。
    /// </summary>
    public static class XmlUtilities
    {
        /// <summary>
        /// 为 <paramref name="node"/> 节点添加名称为 <paramref name="newElementName"/> 的子节点。
        /// </summary>
        /// <param name="node">需要添加子节点的节点</param>
        /// <param name="newElementName">新子节点的名称</param>
        /// <returns>新增的子节点</returns>
        public static XmlNode AppendElement(this XmlNode node, string newElementName)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            return AppendElement(node, newElementName, null);
        }

        /// <summary>
        /// 为 <paramref name="node"/> 节点添加名称为 <paramref name="newElementName"/> 的子节点并设置该子节点的InnerText为 <paramref name="innerValue"/> 。
        /// </summary>
        /// <param name="node">需要添加子节点的节点</param>
        /// <param name="newElementName">新子节点的名称</param>
        /// <param name="innerValue">子节点的InnerText的值</param>
        /// <returns>新增的子节点</returns>
        public static XmlNode AppendElement(this XmlNode node, string newElementName, string innerValue)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            XmlNode oNode;
            if (node is XmlDocument)
                oNode = node.AppendChild(((XmlDocument)node).CreateElement(newElementName));
            else
                oNode = node.AppendChild(node.OwnerDocument.CreateElement(newElementName));
            if (innerValue != null)
                oNode.AppendChild(node.OwnerDocument.CreateTextNode(innerValue));
            return oNode;
        }

        /// <summary>
        /// 为 <paramref name="node"/> 节点添加内容为 <paramref name="innerValue"/> 的CDATA节点
        /// </summary>
        /// <param name="node">需要添加子节点的节点</param>
        /// <param name="innerValue">CDATA节点的内容</param>
        /// <returns>新增的CDATA节点的内容</returns>
        public static XmlCDataSection AppendCDataSection(this XmlNode node, string innerValue)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            XmlCDataSection oNode;

            if (node is XmlDocument)
                node.AppendChild(oNode = ((XmlDocument)node).CreateCDataSection(innerValue));
            else
                node.AppendChild(oNode = node.OwnerDocument.CreateCDataSection(innerValue));
            return oNode;
        }

        /// <summary>
        /// 将 <paramref name="node"/> 节点名称为 <paramref name="attributeName"/> 的属性值设置为 <paramref name="attributeValue"/> 。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <param name="attributeName">属性名称</param>
        /// <param name="attributeValue">新的属性值</param>
        /// <remarks>
        /// 如果该节点不存在名称为 <paramref name="attributeName"/> 的属性，自动为该节点增加名称为 <paramref name="attributeName"/> 的属性并设置该属性值 
        /// </remarks>
        public static void SetAttribute(this XmlNode node, string attributeName, string attributeValue)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            if (node.Attributes[attributeName] != null)
                node.Attributes[attributeName].Value = attributeValue;
            else
                node.Attributes.Append(CreateAttribute(node.OwnerDocument, attributeName, attributeValue));
        }

        /// <summary>
        /// 从节点 <paramref name="node"/> 中读取名称为 <paramref name="name"/> 的属性值。该属性是必须的。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <param name="name">属性名称</param>
        /// <returns>属性值</returns>
        /// <exception cref="XmlException">节点中不包含名称为 <paramref name="name"/> 的属性。</exception>
        public static string GetRequiredAttribute(this XmlNode node, string name)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            return GetRequiredAttribute(node, name, false);
        }

        /// <summary>
        /// 从节点 <paramref name="node"/> 中读取名称为 <paramref name="name"/> 的属性值。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <param name="name">属性名称</param>
        /// <returns>属性值。如果节点中不包含名称为 <paramref name="name"/> 的属性返回 <lang word="null"/>。</returns>
        public static string GetAttribute(this XmlNode node, string name)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            var nodeAttr = node.Attributes.GetNamedItem(name);
            return nodeAttr == null ? null : nodeAttr.Value;
        }

        /// <summary>
        /// 从节点 <paramref name="node"/> 中移除名称为 <paramref name="name"/> 的属性并获取该属性的值。该属性是必须的。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <param name="name">属性名称</param>
        /// <returns>属性值</returns>
        /// <exception cref="XmlException">节点中不包含名称为 <paramref name="name"/> 的属性。</exception>
        public static string RemoveRequiredAttribute(this XmlNode node, string name)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            return RemoveRequiredAttribute(node, name, false);
        }

        /// <summary>
        /// 从节点 <paramref name="node"/> 中移除名称为 <paramref name="name"/> 的属性并获取该属性的值。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <param name="name">属性名称</param>
        /// <returns>属性值</returns>
        public static string RemoveAttribute(this XmlNode node, string name)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            var nodeAttr = node.Attributes.RemoveNamedItem(name);
            return nodeAttr == null ? null : nodeAttr.Value;
        }

        /// <summary>
        /// 验证节点 <paramref name="node"/> 尚未识别的属性。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <exception cref="XmlException">节点 <paramref name="node"/> 中包含未识别的属性。</exception>
        /// <remarks>
        /// <para>
        ///     在使用此方法之前需要使用 <see cref="RemoveRequiredAttribute"/> 方法或 <see cref="RemoveAttribute"/> 方法将可识别的属性移除之后在进行验证，
        /// 剩余的属性都将被认为是不可识别的属性。
        /// </para>
        /// </remarks>
        public static void CheckForUnrecognizedAttributes(this XmlNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            if (node.Attributes.Count != 0)
            {
                throw new XmlException(
                    string.Format("Unrecognized attribute '{0}'. Note that attribute names are case-sensitive.",
                                  node.Attributes[0].Name));
            }
        }

        /// <summary>
        /// 判断节点是否可以忽略并对不可忽略的节点类型进行验证。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <returns>节点是否可以忽略。</returns>
        /// <exception cref="XmlException">节点 <paramref name="node"/> 为不可识别的类型。</exception>
        /// <remarks>
        /// <para>
        ///     节点 <paramref name="node"/> 的类型为 <see cref="XmlNodeType.Comment"/> 或 <see cref="XmlNodeType.Whitespace"/> 时可以忽略。
        /// 否则当类型为 <see cref="XmlNodeType.Element"/> 时节点为可识别类型。
        /// </para>
        /// </remarks>
        public static bool IsIgnorableAlsoCheckForNonElement(this XmlNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            if ((node.NodeType == XmlNodeType.Comment) || (node.NodeType == XmlNodeType.Whitespace))
            {
                return true;
            }
            CheckForNonElement(node);
            return false;
        }

        /// <summary>
        /// 验证节点 <paramref name="node"/> 是否为可识别的 Xml 节点。
        /// </summary>
        /// <param name="node">Xml节点</param>
        /// <exception cref="XmlException">节点 <paramref name="node"/> 为不可识别的类型。</exception>
        /// <remarks>
        /// <para>
        ///     当前只有类型为 <see cref="XmlNodeType.Element"/> 的节点为可识别类型。
        /// </para>
        /// </remarks>
        public static void CheckForNonElement(this XmlNode node)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            if (node.NodeType != XmlNodeType.Element)
            {
                throw new XmlException("Only elements allowed.");
            }
        }

        /// <summary>
        /// 将节点名称 <paramref name="name"/> 转换为有效的 XML 名称。
        /// </summary>
        /// <param name="name">要转换的名称。</param>
        /// <returns>返回名称，任何无效的字符都由转义字符串替换。</returns>
        public static string XmlEncodeName(this string name)
        {
            return XmlConvert.EncodeName(name);
        }

        private static string GetRequiredAttribute(XmlNode node, string name, bool allowEmpty)
        {
            if (node == null)
            {
                throw new ArgumentNullException("node");
            }
            var nodeAttr = node.Attributes.GetNamedItem(name);
            if (nodeAttr == null)
            {
                throw new XmlException(string.Format("Required attribute '{0}' not found.", name));
            }
            if (string.IsNullOrEmpty(nodeAttr.Value) && !allowEmpty)
            {
                throw new XmlException(
                    string.Format("Required attribute '{0}' cannot be empty.", name));
            }
            return nodeAttr.Value;
        }

        /// <summary>
        /// 在XML文档 <see cref="xmlDocument"/> 中创建名称为 <paramref name="name"/> 的属性，并将该属性值设置为 <see cref="value"/>。
        /// </summary>
        /// <param name="xmlDocument">属性节点所属的Document</param>
        /// <param name="name">属性的名称</param>
        /// <param name="value">属性值</param>
        /// <returns>属性节点</returns>
        /// <remarks>
        /// 新增的属性节点不属于任何节点
        /// </remarks>
        private static XmlAttribute CreateAttribute(XmlDocument xmlDocument, string name, string value)
        {
            XmlAttribute oAtt = xmlDocument.CreateAttribute(name);
            oAtt.Value = value;
            return oAtt;
        }

        private static string RemoveRequiredAttribute(XmlNode node, string name, bool allowEmpty)
        {
            XmlNode nodeAttr = node.Attributes.RemoveNamedItem(name);
            if (nodeAttr == null)
            {
                throw new XmlException(string.Format("Required attribute '{0}' not found.", name));
            }
            if (string.IsNullOrEmpty(nodeAttr.Value) && !allowEmpty)
            {
                throw new XmlException(
                    string.Format("Required attribute '{0}' cannot be empty.", name));
            }
            return nodeAttr.Value;
        }
    }
}
